#ifndef     __MSG_QUEUE_H
#define     __MSG_QUEUE_H

#include <queue>
#include <mutex>
#include <condition_variable>

enum class QueueState : uint8_t {
    OK = 0,
    TIMEOUT = 1,
    EXIT = 2,
};

template <typename T>
class MsgQueue {

public:
    MsgQueue(size_t max_size = 5) : m_max_size(max_size) {}

    QueueState push(T&& value) {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_full_cond.wait(lock, [this](){
            return (m_queue.size() < m_max_size) || m_exit_flag;
        });
        if (m_exit_flag) {
            return QueueState::EXIT;
        }
        m_queue.push( std::move(value) );
        m_empty_cond.notify_one();
        return QueueState::OK;
    }

    QueueState pop(T& value) {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_empty_cond.wait(lock, [this](){
            return (!m_queue.empty()) || m_exit_flag;
        });
        if (m_exit_flag) {
            return QueueState::EXIT;
        }
        value = std::move( m_queue.front() );
        m_queue.pop();
        m_full_cond.notify_one();
        return QueueState::OK;
    }

    void exit() {
        std::unique_lock<std::mutex> lock(m_mutex);
        m_exit_flag = true;
        m_empty_cond.notify_one();
        m_full_cond.notify_one();
    }

private:
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_empty_cond;
    std::condition_variable m_full_cond;
    size_t m_max_size;
    bool m_exit_flag = false;
};

#endif
